
#pragma once

#include <functional>


#include "math/Frustum.h"
#include "math/pch.h"

namespace Math
{
    namespace Geometry
    {
        using namespace glm;
        using namespace Math;

        template <class VertexType, class IndexType = unsigned long>
        struct MeshData
        {
            std::vector<VertexType> vertexVec;
            std::vector<IndexType> indexVec;

            MeshData()
            {
                static_assert(sizeof(IndexType) == 2 || sizeof(IndexType) == 4, "The size of IndexType must be 2 bytes or 4 bytes!");
                static_assert(std::is_unsigned<IndexType>::value, "IndexType must be unsigned integer!");
            }
        };
        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateSphere(float radius = 1.0f, unsigned int levels = 20, unsigned int slices = 20,
                                                     const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateBox(float width = 2.0f, float height = 2.0f, float depth = 2.0f,
                                                  const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateBounding(float width = 2.0f, float height = 2.0f, float depth = 2.0f,
                                                       const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateCylinder(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20, unsigned int stacks = 10,
                                                       float texU = 1.0f, float texV = 1.0f, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20, unsigned int stacks = 10,
                                                            float texU = 1.0f, float texV = 1.0f, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateCone(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20,
                                                   const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateConeNoCap(float radius = 1.0f, float height = 2.0f, unsigned int slices = 20,
                                                        const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> Create2DShow(const glm::vec2 &center, const glm::vec2 &scale, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> Create2DShow(float centerX = 0.0f, float centerY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateQuad(const glm::vec2 &center, const glm::vec2 &scale, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateQuad(float centerX = 0.0f, float centerY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreatePlane(const glm::vec2 &planeSize,
                                                    const glm::vec2 &maxTexCoord = {1.0f, 1.0f}, const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});
        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreatePlane(float width = 10.0f, float depth = 10.0f, float texU = 1.0f, float texV = 1.0f,
                                                    const glm::vec4 &color = {1.0f, 1.0f, 1.0f, 1.0f});

        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateTerrain(
            const glm::vec2 &terrainSize,
            const glm::ivec2 &slices = {10, 10}, const glm::vec2 &maxTexCoord = {1.0f, 1.0f},
            const std::function<float(float, float)> &heightFunc = [](float x, float z)
            { return 0.0f; },
            const std::function<glm::vec3(float, float)> &normalFunc = [](float x, float z)
            { return glm::vec3(0.0f, 1.0f, 0.0f); },
            const std::function<glm::vec4(float, float)> &colorFunc = [](float x, float z)
            { return glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); });
        template <class VertexType, class IndexType = unsigned long>
        MeshData<VertexType, IndexType> CreateTerrain(
            float width = 10.0f, float depth = 10.0f,
            unsigned int slicesX = 10, unsigned int slicesZ = 10, float texU = 1.0f, float texV = 1.0f,
            const std::function<float(float, float)> &heightFunc = [](float x, float z)
            { return 0.0f; },
            const std::function<glm::vec3(float, float)> &normalFunc = [](float x, float z)
            { return glm::vec3(0.0f, 1.0f, 0.0f); },
            const std::function<glm::vec4(float, float)> &colorFunc = [](float x, float z)
            { return glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); });

    }

    namespace Geometry
    {
        namespace Internal
        {
            struct VertexData
            {
                glm::vec3 pos;
                glm::vec3 normal;
                glm::vec4 tangent;
                glm::vec4 color;
                glm::vec2 tex;
            };

            template <class VertexType>
            inline void InsertVertexElement(VertexType &vertexDst, const VertexData &vertexSrc)
            {
                static std::string semanticName;
                static const std::map<std::string, std::pair<size_t, size_t>> semanticSizeMap = {
                    {"POSITION", std::pair<size_t, size_t>(0, 12)},
                    {"NORMAL", std::pair<size_t, size_t>(12, 24)},
                    {"TANGENT", std::pair<size_t, size_t>(24, 40)},
                    {"COLOR", std::pair<size_t, size_t>(40, 56)},
                    {"TEXCOORD", std::pair<size_t, size_t>(56, 64)}};

                for (size_t i = 0; i < VertexType::inputLayout.size(); i++)
                {
                    semanticName = VertexType::inputLayout[i].name;
                    const auto &range = semanticSizeMap.at(semanticName);
                    memcpy(reinterpret_cast<char *>(&vertexDst) + VertexType::inputLayout[i].offset,
                           reinterpret_cast<const char *>(&vertexSrc) + range.first,
                           range.second - range.first);
                }
            }
        }

        //

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreateSphere(float radius, unsigned int levels, unsigned int slices, const glm::vec4 &color)
        {
            MeshData<VertexType, IndexType> meshData;
            unsigned int vertexCount = 2 + (levels - 1) * (slices + 1);
            unsigned int indexCount = 6 * (levels - 1) * slices;
            meshData.vertexVec.resize(vertexCount);
            meshData.indexVec.resize(indexCount);

            Internal::VertexData vertexData;
            IndexType vIndex = 0, iIndex = 0;

            float phi = 0.0f, theta = 0.0f;
            float per_phi = glm::pi<float>() / levels;
            float per_theta = glm::pi<float>() * 2.0f / slices;
            float x, y, z;

            vertexData = {glm::vec3(0.0f, radius, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            for (unsigned int i = 1; i < levels; ++i)
            {
                phi = per_phi * i;
                for (unsigned int j = 0; j <= slices; ++j)
                {
                    theta = per_theta * j;
                    x = radius * sinf(phi) * cosf(theta);
                    y = radius * cosf(phi);
                    z = radius * sinf(phi) * sinf(theta);

                    glm::vec3 pos = glm::vec3(x, y, z), normal;
                    // XMStoreFloat3(&normal, XMVector3Normalize(XMLoadFloat3(&pos)));
                    normal = glm::normalize(pos);

                    vertexData = {pos, normal, glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(theta / glm::pi<float>() * 2.0f, phi / glm::pi<float>())};
                    Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
                }
            }

            vertexData = {glm::vec3(0.0f, -radius, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f),
                          glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 1.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            if (levels > 1)
            {
                for (unsigned int j = 1; j <= slices; ++j)
                {
                    meshData.indexVec[iIndex++] = 0;
                    meshData.indexVec[iIndex++] = j % (slices + 1) + 1;
                    meshData.indexVec[iIndex++] = j;
                }
            }

            for (unsigned int i = 1; i < levels - 1; ++i)
            {
                for (unsigned int j = 1; j <= slices; ++j)
                {
                    meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
                    meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j % (slices + 1) + 1;
                    meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;

                    meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;
                    meshData.indexVec[iIndex++] = i * (slices + 1) + j;
                    meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
                }
            }

            if (levels > 1)
            {
                for (unsigned int j = 1; j <= slices; ++j)
                {
                    meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j;
                    meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j % (slices + 1) + 1;
                    meshData.indexVec[iIndex++] = (levels - 1) * (slices + 1) + 1;
                }
            }

            return meshData;
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreateFrustum(Frustum f = {45.0f, 1.0f, 1.0f, 50.0f})
        {
            using namespace glm;

            // w/h
            float nearZ = f.nNear;
            float farZ = f.nFar;

            float nearY = glm::tan(glm::radians(f.fovy / 2.0f)) * f.nNear;
            float nearX = nearY * f.aspect;

            float farY = glm::tan(glm::radians(f.fovy / 2.0f)) * f.nFar;
            float farX = farY * f.aspect;

            MeshData<VertexType, IndexType> meshData;
            meshData.vertexVec.resize(24);

            Internal::VertexData vertexDataArr[24];

            std::vector<glm::vec3> positions =
                {
                    // near Plane
                    glm::vec3(-nearX, -nearY, nearZ),
                    glm::vec3(nearX, -nearY, nearZ),
                    glm::vec3(nearX, nearY, nearZ),
                    glm::vec3(-nearX, nearY, nearZ),

                    // far Plane
                    glm::vec3(-farX, -farY, farZ),
                    glm::vec3(farX, -farY, farZ),
                    glm::vec3(farX, farY, farZ),
                    glm::vec3(-farX, farY, farZ),

                };
            vertexDataArr[0].pos = positions[1];
            vertexDataArr[1].pos = positions[2];
            vertexDataArr[2].pos = positions[6];
            vertexDataArr[3].pos = positions[5];
            vertexDataArr[4].pos = positions[0];
            vertexDataArr[5].pos = positions[3];
            vertexDataArr[6].pos = positions[7];
            vertexDataArr[7].pos = positions[4];
            vertexDataArr[16].pos = positions[4];
            vertexDataArr[17].pos = positions[5];
            vertexDataArr[18].pos = positions[6];
            vertexDataArr[19].pos = positions[7];
            vertexDataArr[20].pos = positions[0];
            vertexDataArr[21].pos = positions[1];
            vertexDataArr[22].pos = positions[2];
            vertexDataArr[23].pos = positions[3];

            for (unsigned int i = 0; i < 24; ++i)
            {
                Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
            }

            meshData.indexVec = {
                0, 1, 1, 2, 2, 3, 3, 0,
                4, 5, 5, 6, 6, 7, 7, 4,
                16, 17, 17, 18, 18, 19, 19, 16,
                20, 21, 21, 22, 22, 23, 23, 20};

            return meshData;
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreateBox(float width, float height, float depth, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            meshData.vertexVec.resize(24);

            Internal::VertexData vertexDataArr[24];
            float w2 = width / 2, h2 = height / 2, d2 = depth / 2;

            vertexDataArr[0].pos = glm::vec3(w2, -h2, -d2);
            vertexDataArr[1].pos = glm::vec3(w2, h2, -d2);
            vertexDataArr[2].pos = glm::vec3(w2, h2, d2);
            vertexDataArr[3].pos = glm::vec3(w2, -h2, d2);
            vertexDataArr[4].pos = glm::vec3(-w2, -h2, d2);
            vertexDataArr[5].pos = glm::vec3(-w2, h2, d2);
            vertexDataArr[6].pos = glm::vec3(-w2, h2, -d2);
            vertexDataArr[7].pos = glm::vec3(-w2, -h2, -d2);

            vertexDataArr[8].pos = glm::vec3(-w2, h2, -d2);
            vertexDataArr[9].pos = glm::vec3(-w2, h2, d2);
            vertexDataArr[10].pos = glm::vec3(w2, h2, d2);
            vertexDataArr[11].pos = glm::vec3(w2, h2, -d2);
            vertexDataArr[12].pos = glm::vec3(w2, -h2, -d2);
            vertexDataArr[13].pos = glm::vec3(w2, -h2, d2);
            vertexDataArr[14].pos = glm::vec3(-w2, -h2, d2);
            vertexDataArr[15].pos = glm::vec3(-w2, -h2, -d2);
            vertexDataArr[16].pos = glm::vec3(w2, -h2, d2);
            vertexDataArr[17].pos = glm::vec3(w2, h2, d2);
            vertexDataArr[18].pos = glm::vec3(-w2, h2, d2);
            vertexDataArr[19].pos = glm::vec3(-w2, -h2, d2);
            vertexDataArr[20].pos = glm::vec3(-w2, -h2, -d2);
            vertexDataArr[21].pos = glm::vec3(-w2, h2, -d2);
            vertexDataArr[22].pos = glm::vec3(w2, h2, -d2);
            vertexDataArr[23].pos = glm::vec3(w2, -h2, -d2);

            for (unsigned int i = 0; i < 4; ++i)
            {
                vertexDataArr[i].normal = glm::vec3(1.0f, 0.0f, 0.0f);
                vertexDataArr[i].tangent = glm::vec4(0.0f, 0.0f, 1.0f, 1.0f);
                vertexDataArr[i].color = color;
                vertexDataArr[i + 4].normal = glm::vec3(-1.0f, 0.0f, 0.0f);
                vertexDataArr[i + 4].tangent = glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
                vertexDataArr[i + 4].color = color;
                vertexDataArr[i + 8].normal = glm::vec3(0.0f, 1.0f, 0.0f);
                vertexDataArr[i + 8].tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
                vertexDataArr[i + 8].color = color;
                vertexDataArr[i + 12].normal = glm::vec3(0.0f, -1.0f, 0.0f);
                vertexDataArr[i + 12].tangent = glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f);
                vertexDataArr[i + 12].color = color;
                vertexDataArr[i + 16].normal = glm::vec3(0.0f, 0.0f, 1.0f);
                vertexDataArr[i + 16].tangent = glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f);
                vertexDataArr[i + 16].color = color;
                vertexDataArr[i + 20].normal = glm::vec3(0.0f, 0.0f, -1.0f);
                vertexDataArr[i + 20].tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
                vertexDataArr[i + 20].color = color;
            }

            for (unsigned int i = 0; i < 6; ++i)
            {
                vertexDataArr[i * 4].tex = glm::vec2(0.0f, 1.0f);
                vertexDataArr[i * 4 + 1].tex = glm::vec2(0.0f, 0.0f);
                vertexDataArr[i * 4 + 2].tex = glm::vec2(1.0f, 0.0f);
                vertexDataArr[i * 4 + 3].tex = glm::vec2(1.0f, 1.0f);
            }

            for (unsigned int i = 0; i < 24; ++i)
            {
                Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
            }

            meshData.indexVec = {
                0, 1, 2, 2, 3, 0,
                4, 5, 6, 6, 7, 4,
                8, 9, 10, 10, 11, 8,
                12, 13, 14, 14, 15, 12,
                16, 17, 18, 18, 19, 16,
                20, 21, 22, 22, 23, 20};

            return meshData;
        }

        // Create Cube Rect
        template <class VertexType, class IndexType>
        MeshData<VertexType, IndexType> CreateBounding(float width, float height, float depth, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            meshData.vertexVec.resize(24);

            Internal::VertexData vertexDataArr[24];
            float w2 = width / 2, h2 = height / 2, d2 = depth / 2;

            vertexDataArr[0].pos = glm::vec3(w2, -h2, -d2);
            vertexDataArr[1].pos = glm::vec3(w2, h2, -d2);
            vertexDataArr[2].pos = glm::vec3(w2, h2, d2);
            vertexDataArr[3].pos = glm::vec3(w2, -h2, d2);
            vertexDataArr[4].pos = glm::vec3(-w2, -h2, d2);
            vertexDataArr[5].pos = glm::vec3(-w2, h2, d2);
            vertexDataArr[6].pos = glm::vec3(-w2, h2, -d2);
            vertexDataArr[7].pos = glm::vec3(-w2, -h2, -d2);
            vertexDataArr[8].pos = glm::vec3(-w2, h2, -d2);
            vertexDataArr[9].pos = glm::vec3(-w2, h2, d2);
            vertexDataArr[10].pos = glm::vec3(w2, h2, d2);
            vertexDataArr[11].pos = glm::vec3(w2, h2, -d2);
            vertexDataArr[12].pos = glm::vec3(w2, -h2, -d2);
            vertexDataArr[13].pos = glm::vec3(w2, -h2, d2);
            vertexDataArr[14].pos = glm::vec3(-w2, -h2, d2);
            vertexDataArr[15].pos = glm::vec3(-w2, -h2, -d2);
            vertexDataArr[16].pos = glm::vec3(w2, -h2, d2);
            vertexDataArr[17].pos = glm::vec3(w2, h2, d2);
            vertexDataArr[18].pos = glm::vec3(-w2, h2, d2);
            vertexDataArr[19].pos = glm::vec3(-w2, -h2, d2);
            vertexDataArr[20].pos = glm::vec3(-w2, -h2, -d2);
            vertexDataArr[21].pos = glm::vec3(-w2, h2, -d2);
            vertexDataArr[22].pos = glm::vec3(w2, h2, -d2);
            vertexDataArr[23].pos = glm::vec3(w2, -h2, -d2);

            for (unsigned int i = 0; i < 24; ++i)
            {
                Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
            }

            meshData.indexVec = {
                0, 1, 1, 2, 2, 3, 3, 0,
                4, 5, 5, 6, 6, 7, 7, 4,
                // 8, 9,9,10, 10, 11,11,8 ,
                // 12, 13,13,14 ,14, 15,15,12 ,
                16, 17, 17, 18, 18, 19, 19, 16,
                20, 21, 21, 22, 22, 23, 23, 20};

            return meshData;
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreateCylinder(float radius, float height, unsigned int slices, unsigned int stacks,
                                                              float texU, float texV, const glm::vec4 &color)
        {
            using namespace glm;

            auto meshData = CreateCylinderNoCap<VertexType, IndexType>(radius, height, slices, stacks, texU, texV, color);
            unsigned int vertexCount = (slices + 1) * (stacks + 3) + 2;
            unsigned int indexCount = 6 * slices * (stacks + 1);
            meshData.vertexVec.resize(vertexCount);
            meshData.indexVec.resize(indexCount);

            float h2 = height / 2;
            float theta = 0.0f;
            float per_theta = glm::pi<float>() * 2.0f / slices;

            IndexType vIndex = (slices + 1) * (stacks + 1), iIndex = 6 * slices * stacks;
            IndexType offset = vIndex;
            Internal::VertexData vertexData;
            vertexData = {glm::vec3(0.0f, h2, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.5f, 0.5f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            for (unsigned int i = 0; i <= slices; ++i)
            {
                theta = i * per_theta;
                float u = cosf(theta) * radius / height + 0.5f;
                float v = sinf(theta) * radius / height + 0.5f;
                vertexData = {glm::vec3(radius * cosf(theta), h2, radius * sinf(theta)), glm::vec3(0.0f, 1.0f, 0.0f),
                              glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(u, v)};
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }

            vertexData = {glm::vec3(0.0f, -h2, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f),
                          glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.5f, 0.5f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            for (unsigned int i = 0; i <= slices; ++i)
            {
                theta = i * per_theta;
                float u = cosf(theta) * radius / height + 0.5f;
                float v = sinf(theta) * radius / height + 0.5f;
                vertexData = {glm::vec3(radius * cosf(theta), -h2, radius * sinf(theta)), glm::vec3(0.0f, -1.0f, 0.0f),
                              glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(u, v)};
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }

            for (unsigned int i = 1; i <= slices; ++i)
            {
                meshData.indexVec[iIndex++] = offset;
                meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
                meshData.indexVec[iIndex++] = offset + i;
            }

            offset += slices + 2;
            for (unsigned int i = 1; i <= slices; ++i)
            {
                meshData.indexVec[iIndex++] = offset;
                meshData.indexVec[iIndex++] = offset + i;
                meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
            }

            return meshData;
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius, float height, unsigned int slices, unsigned int stacks,
                                                                   float texU, float texV, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            unsigned int vertexCount = (slices + 1) * (stacks + 1);
            unsigned int indexCount = 6 * slices * stacks;
            meshData.vertexVec.resize(vertexCount);
            meshData.indexVec.resize(indexCount);

            float h2 = height / 2;
            float theta = 0.0f;
            float per_theta = glm::pi<float>() * 2.0f / slices;
            float stackHeight = height / stacks;

            Internal::VertexData vertexData;

            unsigned int vIndex = 0;
            for (unsigned int i = 0; i < stacks + 1; ++i)
            {
                float y = -h2 + i * stackHeight;
                for (unsigned int j = 0; j <= slices; ++j)
                {
                    theta = j * per_theta;
                    float u = theta / glm::pi<float>() * 2.0f;
                    float v = 1.0f - (float)i / stacks;
                    vertexData = {glm::vec3(radius * cosf(theta), y, radius * sinf(theta)), glm::vec3(cosf(theta), 0.0f, sinf(theta)),
                                  glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(u * texU, v * texV)};
                    Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
                }
            }

            unsigned int iIndex = 0;
            for (unsigned int i = 0; i < stacks; ++i)
            {
                for (unsigned int j = 0; j < slices; ++j)
                {
                    meshData.indexVec[iIndex++] = i * (slices + 1) + j;
                    meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j;
                    meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j + 1;

                    meshData.indexVec[iIndex++] = i * (slices + 1) + j;
                    meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j + 1;
                    meshData.indexVec[iIndex++] = i * (slices + 1) + j + 1;
                }
            }

            return meshData;
        }

        template <class VertexType, class IndexType>
        MeshData<VertexType, IndexType> CreateCone(float radius, float height, unsigned int slices, const glm::vec4 &color)
        {
            using namespace glm;
            auto meshData = CreateConeNoCap<VertexType, IndexType>(radius, height, slices, color);

            unsigned int vertexCount = 3 * slices + 1;
            unsigned int indexCount = 6 * slices;
            meshData.vertexVec.resize(vertexCount);
            meshData.indexVec.resize(indexCount);

            float h2 = height / 2;
            float theta = 0.0f;
            float per_theta = glm::pi<float>() * 2.0f / slices;
            unsigned int iIndex = 3 * slices;
            unsigned int vIndex = 2 * slices;
            Internal::VertexData vertexData;

            for (unsigned int i = 0; i < slices; ++i)
            {
                theta = i * per_theta;
                vertexData = {glm::vec3(radius * cosf(theta), -h2, radius * sinf(theta)), glm::vec3(0.0f, -1.0f, 0.0f),
                              glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f)};
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }
            vertexData = {glm::vec3(0.0f, -h2, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f),
                          glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.5f, 0.5f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            unsigned int offset = 2 * slices;
            for (unsigned int i = 0; i < slices; ++i)
            {
                meshData.indexVec[iIndex++] = offset + slices;
                meshData.indexVec[iIndex++] = offset + i % slices;
                meshData.indexVec[iIndex++] = offset + (i + 1) % slices;
            }

            return meshData;
        }

        template <class VertexType, class IndexType>
        MeshData<VertexType, IndexType> CreateConeNoCap(float radius, float height, unsigned int slices, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            unsigned int vertexCount = 2 * slices;
            unsigned int indexCount = 3 * slices;
            meshData.vertexVec.resize(vertexCount);
            meshData.indexVec.resize(indexCount);

            float h2 = height / 2;
            float theta = 0.0f;
            float per_theta = glm::pi<float>() * 2.0f / slices;
            float len = sqrtf(height * height + radius * radius);
            unsigned int iIndex = 0;
            unsigned int vIndex = 0;
            Internal::VertexData vertexData;

            for (unsigned int i = 0; i < slices; ++i)
            {
                theta = i * per_theta + per_theta / 2;
                vertexData = {glm::vec3(0.0f, h2, 0.0f), glm::vec3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
                              glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(0.5f, 0.5f)};
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }

            for (unsigned int i = 0; i < slices; ++i)
            {
                theta = i * per_theta;
                vertexData = {glm::vec3(radius * cosf(theta), -h2, radius * sinf(theta)), glm::vec3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
                              glm::vec4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, glm::vec2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f)};
                Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
            }

            for (unsigned int i = 0; i < slices; ++i)
            {
                meshData.indexVec[iIndex++] = i;
                meshData.indexVec[iIndex++] = slices + (i + 1) % slices;
                meshData.indexVec[iIndex++] = slices + i % slices;
            }

            return meshData;
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> Create2DShow(const glm::vec2 &center, const glm::vec2 &scale, const glm::vec4 &color)
        {
            return Create2DShow<VertexType, IndexType>(center.x, center.y, scale.x, scale.y, color);
        }

        template <class VertexType, class IndexType>
        MeshData<VertexType, IndexType> CreateQuad(const glm::vec2 &center, const glm::vec2 &scale, const glm::vec4 &color)
        {
            return CreateQuad<VertexType, IndexType>(center.x, center.y, scale.x, scale.y, color);
        }
        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreateQuad(float centerX, float centerY, float scaleX, float scaleY, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            meshData.vertexVec.resize(4);

            Internal::VertexData vertexData;
            unsigned int vIndex = 0;

            vertexData = {glm::vec3(centerX - scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(centerX - scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 1.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(centerX + scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 1.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(centerX + scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            meshData.indexVec = {0, 1, 2, 2, 3, 0};
            return meshData;
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> Create2DShow(float centerX, float centerY, float scaleX, float scaleY, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            meshData.vertexVec.resize(4);

            Internal::VertexData vertexData;
            unsigned int vIndex = 0;

            vertexData = {glm::vec3(centerX - scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 1.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(centerX - scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(centerX + scaleX, centerY + scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(centerX + scaleX, centerY - scaleY, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(1.0f, 1.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            meshData.indexVec = {0, 1, 2, 2, 3, 0};
            return meshData;
        }
        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreatePlane(const glm::vec2 &planeSize,
                                                           const glm::vec2 &maxTexCoord, const glm::vec4 &color)
        {
            return CreatePlane<VertexType, IndexType>(planeSize.x, planeSize.y, maxTexCoord.x, maxTexCoord.y, color);
        }

        template <class VertexType, class IndexType>
        inline MeshData<VertexType, IndexType> CreatePlane(float width, float depth, float texU, float texV, const glm::vec4 &color)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            meshData.vertexVec.resize(4);

            Internal::VertexData vertexData;
            unsigned int vIndex = 0;

            vertexData = {glm::vec3(-width / 2, 0.0f, -depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, texV)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(-width / 2, 0.0f, depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(0.0f, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(width / 2, 0.0f, depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(texU, 0.0f)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            vertexData = {glm::vec3(width / 2, 0.0f, -depth / 2), glm::vec3(0.0f, 1.0f, 0.0f),
                          glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), color, glm::vec2(texU, texV)};
            Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

            meshData.indexVec = {0, 1, 2, 2, 3, 0};
            return meshData;
        }

        template <class VertexType, class IndexType>
        MeshData<VertexType, IndexType> CreateTerrain(const glm::vec2 &terrainSize, const glm::ivec2 &slices,
                                                      const glm::vec2 &maxTexCoord, const std::function<float(float, float)> &heightFunc,
                                                      const std::function<glm::vec3(float, float)> &normalFunc,
                                                      const std::function<glm::vec4(float, float)> &colorFunc)
        {
            return CreateTerrain<VertexType, IndexType>(terrainSize.x, terrainSize.y, slices.x, slices.y,
                                                        maxTexCoord.x, maxTexCoord.y, heightFunc, normalFunc, colorFunc);
        }

        template <class VertexType, class IndexType>
        MeshData<VertexType, IndexType> CreateTerrain(float width, float depth, unsigned int slicesX, unsigned int slicesZ,
                                                      float texU, float texV, const std::function<float(float, float)> &heightFunc,
                                                      const std::function<glm::vec3(float, float)> &normalFunc,
                                                      const std::function<glm::vec4(float, float)> &colorFunc)
        {
            using namespace glm;

            MeshData<VertexType, IndexType> meshData;
            unsigned int vertexCount = (slicesX + 1) * (slicesZ + 1);
            unsigned int indexCount = 6 * slicesX * slicesZ;
            meshData.vertexVec.resize(vertexCount);
            meshData.indexVec.resize(indexCount);

            Internal::VertexData vertexData;
            unsigned int vIndex = 0;
            unsigned int iIndex = 0;

            float sliceWidth = width / slicesX;
            float sliceDepth = depth / slicesZ;
            float leftBottomX = -width / 2;
            float leftBottomZ = -depth / 2;
            float posX, posZ;
            float sliceTexWidth = texU / slicesX;
            float sliceTexDepth = texV / slicesZ;

            glm::vec3 normal;
            glm::vec4 tangent;
            // �������񶥵�
            //  __ __
            // | /| /|
            // |/_|/_|
            // | /| /|
            // |/_|/_|
            for (unsigned int z = 0; z <= slicesZ; ++z)
            {
                posZ = leftBottomZ + z * sliceDepth;
                for (unsigned int x = 0; x <= slicesX; ++x)
                {
                    posX = leftBottomX + x * sliceWidth;
                    normal = normalFunc(posX, posZ);
                    normal = glm::normalize(normal);
                    tangent = glm::vec4(normal.y, -normal.x, 0.f, 1.0f) + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
                    ;

                    vertexData = {glm::vec3(posX, heightFunc(posX, posZ), posZ),
                                  normal, tangent, colorFunc(posX, posZ), glm::vec2(x * sliceTexWidth, texV - z * sliceTexDepth)};
                    Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
                }
            }
            for (unsigned int i = 0; i < slicesZ; ++i)
            {
                for (unsigned int j = 0; j < slicesX; ++j)
                {
                    meshData.indexVec[iIndex++] = i * (slicesX + 1) + j;
                    meshData.indexVec[iIndex++] = (i + 1) * (slicesX + 1) + j;
                    meshData.indexVec[iIndex++] = (i + 1) * (slicesX + 1) + j + 1;

                    meshData.indexVec[iIndex++] = (i + 1) * (slicesX + 1) + j + 1;
                    meshData.indexVec[iIndex++] = i * (slicesX + 1) + j + 1;
                    meshData.indexVec[iIndex++] = i * (slicesX + 1) + j;
                }
            }

            return meshData;
        }

    }
}